package edu.vt.ece.rt.benchmark.hashmap;

import java.util.ArrayList;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import org.jgroups.annotations.LocalAddress;

import edu.vt.ece.rt.dstm.Context;
import edu.vt.ece.rt.dstm.ContextDelegator;
import edu.vt.ece.rt.dstm.QObject;
import edu.vt.ece.rt.dstm.TMProxy;
import edu.vt.ece.rt.quorum.ClustersManager;
import edu.vt.ece.rt.quorum.Resource;
import edu.vt.ece.rt.quorum.util.AbortException;
import edu.vt.ece.rt.quorum.util.Names;
import edu.vt.ece.rt.util.Logger.Logger;
import edu.vt.ece.rt.benchmark.BenchmarkThread;

public class Hashmap extends QObject {

	ArrayList<Integer> l;
	private String id;
	static int bucketNums;

	public Hashmap() {
		l = new ArrayList<Integer>();		
	}

	public Hashmap(String ID, ArrayList<Integer> arr) {
		id = ID;
		l = arr;
		Resource.putQObject(getId(), this);
	}

	public Hashmap(String ID) {
		id = ID;
		l = new ArrayList<Integer>();
		Resource.putQObject(getId(), this);
	}
	public static void setBuckets(int n) {
		bucketNums = n;
	}

	@Override
	public QObject clone() {
		Hashmap h = new Hashmap();
		h.setId(getId());
		h.setVersion(getVersion());
		synchronized (Resource.waitObj) {
			for(Integer i : this.l) {
				h.getList().add(new Integer(i));
			}
		}
		h.setCloneOwner(getCloneOwner());
		return h;
	}

	@Override
	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public static int hash(int val){
		int bucket = 0;

		bucket = val % bucketNums;		
		return bucket;
	}

	public ArrayList<Integer> getList(){
		return l;
	}

	public static Boolean find(int val, Context parentContext) {

		Boolean found = null;
		Long priority = System.currentTimeMillis();

		for (int i = 0; i < 0x7fffffff; i++) {

			((BenchmarkThread)Thread.currentThread()).collectStatsRoot(i);
			Context context = ContextDelegator.getNestedContext();
			context.setParent(parentContext);
			context.setDescription("find element in bucket");
			context.sanityNested();
			context.setPriority(priority);

			Logger.debug("----Started----- R Transaction "+context.getContextId()+" on "+ClustersManager.baseClusterId);
			try {

				int bucket = hash(val);
				String bucketId = "bucket-"+bucket;
				Hashmap map = (Hashmap) TMProxy.getTMProxy().read(bucketId, context.getContextId());

				for (Integer entry : map.getList()) {
					if (entry.intValue() == val) {
						break;
					}
				}

				if (TMProxy.getTMProxy().requestCommit(context.getContextId())) {
					context.unsetParent(parentContext);
					return found;
				}
			} catch (AbortException e) {
				Logger.debug("Aborting in loop "+context.getContextId());
				e.touch();
			}
		}
		Logger.fetal("Unable to complete transaction in given number of tries");

		return found;
	}

	public static void addWork(int val, Context context) throws AbortException {
		int bucket = hash(val);
		String bucketId = "bucket-"+bucket;
		Hashmap map = (Hashmap) TMProxy.getTMProxy().write(bucketId, context.getContextId());

		boolean found = false;
		for (Integer entry : map.getList()) {
			if (entry.intValue() == val) {
				found = true;
			}
		}

		if (!found) {
			map.getList().add(new Integer(val));
		}
	}

	public static Boolean add(int val, Context parentContext) throws AbortException{

		for (int i = 0; i < 0x7fffffff; i++) {


			((BenchmarkThread)Thread.currentThread()).collectStatsInner(i);
			Context context = ContextDelegator.getNestedContext();
			context.setParent(parentContext);
			context.setDescription("add element "+val+" in map");
			context.sanityNested();

			Logger.debug("----Started----- R Transaction "+context.getContextId()+" on "+ClustersManager.baseClusterId);
			try {
				addWork(val, context);
				context.mergeParent();
				return true;
			} catch (AbortException e) {
				if (e.getTransaction().longValue() != context.getContextId().longValue()) {
					Logger.debug("Exception in parent transaction "+e.getTransaction());
					//					System.out.println("Exception in parent transaction "+e.getTransaction());
					throw e;
				} else {
					Logger.debug("Exception in current transaction "+e.getTransaction());
					//					System.out.println("Exception in current transaction "+e.getTransaction());					
				}

			}
		}

		return false;
	}

	public static void deleteWork(int val, Context context) throws AbortException {
		int bucket = hash(val);
		String bucketId = "bucket-"+bucket;
		Hashmap map = (Hashmap) TMProxy.getTMProxy().write(bucketId, context.getContextId());
		Integer newEntry = null;
		for (Integer entry : map.getList()) {
			if (entry.intValue() == val) {
				newEntry = entry;
			}
		}
		if (newEntry != null)
			map.getList().remove(newEntry);

	}

	public static Boolean delete(int val, Context parentContext) throws AbortException {

		for (int i = 0; i < 0x7fffffff; i++) {

			((BenchmarkThread)Thread.currentThread()).collectStatsInner(i);
			Context context = ContextDelegator.getNestedContext();
			context.setParent(parentContext);
			context.setDescription("delete element "+val +" in map");
			context.sanityNested();

			Logger.debug("----Started----- R Transaction "+context.getContextId()+" on "+ClustersManager.baseClusterId);
			try {

				deleteWork(val, context);
				context.mergeParent();
				return true;
			} catch (AbortException e) {
				if (e.getTransaction().longValue() != context.getContextId().longValue()) {
					Logger.debug("Exception in parent transaction "+e.getTransaction());
					//					System.out.println("Exception in parent transaction "+e.getTransaction());
					throw e;
				} else {
					ContextDelegator.removeContext(context);
					Logger.debug("Exception in current transaction "+e.getTransaction());
					//					System.out.println("Exception in current transaction "+e.getTransaction());					
				}

			}
		}
		return false;
	}


	public static void closedNestedHashmap(int localObjectsCount, boolean nesting) {
		Context parentContext = null;
		Long priority = System.currentTimeMillis();
		
		ArrayList<Integer> arr = new ArrayList<Integer>();
		ArrayList<Boolean> booleanVal = new ArrayList<Boolean>();

		Random random = new Random();
		for (int j = 0; j < Integer.getInteger(Names.calls); j++ ) {
			arr.add(random.nextInt(localObjectsCount));

			if (random.nextInt(100) > 50) {
				booleanVal.add(true);
			} else {
				booleanVal.add(false);
			}

		}

		for (int i = 0; i < 0x7fffffff; i++) {

			((BenchmarkThread)Thread.currentThread()).collectStatsRoot(i);
			Context context = ContextDelegator.getNestedContext();
			context.setParent(parentContext);
			context.setDescription("starting root transaction");
			context.sanityNested();
			context.setPriority(priority);

			if (!nesting) {
				Random r = new Random();

				try {
					int sleepTime = r.nextInt(100)*(ClustersManager.baseClusterId + 1);
					Thread.sleep(sleepTime);
				} catch (InterruptedException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}


			}

			Logger.debug("----Started----- R Transaction "+context.getContextId()+" on "+ClustersManager.baseClusterId);
			try {

				for (int j = 0; j < Integer.getInteger(Names.calls);j++)  {

					int val = arr.get(j);
					boolean delOp = booleanVal.get(j); 


					if (delOp) {
						Logger.debug("delete op started on "+ClustersManager.baseClusterId);
						if (nesting)
							Hashmap.delete(val, context);
						else
							Hashmap.deleteWork(val, context);
					} else {
						Logger.debug("delete op started on "+ClustersManager.baseClusterId);
						if (nesting)
							Hashmap.add(val, context);
						else
							Hashmap.addWork(val, context);
					}
				}

				if (TMProxy.getTMProxy().requestCommit(context.getContextId())) {
					context.unsetParent(parentContext);
					return;
				}

			}  catch (AbortException e) {
				e.touch();
			}
		}

	}



	static void checkBuckets(Context parentContext) {

		for (int i = 0; i < 0x7fffffff; i++) {
			if (i > 0) {
				((BenchmarkThread)Thread.currentThread()).incrementAborts();
			}
			Context context = ContextDelegator.getNestedContext();
			context.setParent(parentContext);
			context.setDescription("delete element in list");
			context.sanityNested();

			Logger.debug("----Started----- R Transaction "+context.getContextId()+" on "+ClustersManager.baseClusterId);

			try {

				for (int bucket = 0; bucket < bucketNums; bucket++) {
					String bucketId = "bucket-"+bucket;
					//					Hashmap map = (Hashmap) TMProxy.getTMProxy().read(bucketId, context.getContextId());
					Hashmap map = (Hashmap) Resource.getQObject(bucketId);
					for (Integer entry : map.getList()) {
						Logger.debug("Entry in bucket "+bucket+" is "+entry);
						if (hash(entry) != bucket) {
							System.out.println("fatal error");
						}
					}
				}

				if (TMProxy.getTMProxy().requestCommit(context.getContextId())) {
					context.unsetParent(parentContext);
					return;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}

		}	
		return;
	}



}
